import React, { useState, useEffect } from "react";
import {
Home, Users, CalendarCheck, MessageSquareText, PenTool, Star, Plus, Check, X,
ChevronLeft, Sparkles, LayoutDashboard, ClipboardList, FolderKanban, Mic,
GraduationCap, ScrollText, ClipboardCheck, Inbox, Bell, UserRound, FileBarChart,
QrCode, LogOut, Upload, Lock, Flame, BookOpen, Sprout, ShieldCheck,
} from "lucide-react";
/* ================= design tokens ================= */
const C = {
bg: "#FBF7EC", card: "#FFFFFF", ink: "#2B2A1E", inkSoft: "#6B6650",
olive: "#4E5B27", oliveDark: "#30391A", oliveSoft: "#EAEFDD",
gold: "#C9972E", goldSoft: "#F4E4B8",
indigo: "#33447A", indigoSoft: "#E4E7F2",
pink: "#E8A9BE", pinkSoft: "#FBEAF1",
aqua: "#3FA79A", aquaSoft: "#E1F2EE",
red: "#B5442E", redSoft: "#FBEAE5",
line: "#E7E0CC",
};
const FONTS = `
@import url('https://fonts.googleapis.com/css2?family=Reem+Kufi:wght@400..700&family=Tajawal:wght@300;400;500;700;900&display=swap');
.font-display{font-family:'Reem Kufi',sans-serif}
.font-body{font-family:'Tajawal',sans-serif}
@keyframes marquee-rtl{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}
.marquee-track{animation:marquee-rtl 34s linear infinite;white-space:nowrap}
@media (prefers-reduced-motion:reduce){.marquee-track{animation:none}}
.star-badge{transition:transform .15s ease}
.star-badge:hover{transform:scale(1.15) rotate(8deg)}
input:focus,textarea:focus,select:focus,button:focus-visible{outline:2px solid ${C.gold};outline-offset:2px}
`;
function OctaStar({ size = 16, fill = C.gold }) {
return (
);
}
/* ================= constants ================= */
const CLASSES = ["أول متوسط/3", "أول متوسط/4", "أول متوسط/5", "أول متوسط/6"];
const MARQUEE_TEXT = "كن كمن يمشي على الرمل، يرى أثر خطواته، لكن لا تُسمع له صوتاً";
const NAMES = [
"لمى العتيبي", "جوري الحربي", "غلا القحطاني", "دانة السبيعي", "ريتاج الشمري", "نوف الدوسري",
"شهد المطيري", "روان الزهراني", "سدن العنزي", "مهى الغامدي", "أثير الحارثي", "وعد البقمي",
"ريناد العصيمي", "جنى المالكي", "تالا الرشيدي", "لجين آل سعد", "بشاير الجهني", "رغد الشهري",
"أروى القرني", "هيا العمري", "سلمى الفيفي", "نجد الخالدي", "ليان السلمي", "رهف الحازمي",
];
function makeInitialDB() {
const students = NAMES.map((name, i) => ({
id: i + 1,
name,
cls: CLASSES[i % CLASSES.length],
pin: "1111",
parentName: "ولي أمر " + name.split(" ")[0],
points: 4 + ((i * 3) % 15),
attendance: {},
behaviorLog: [],
skills: { reading: 1 + (i % 4), handwriting: 1 + ((i + 1) % 4) },
}));
const tasks = [
{ id: 1, type: "واجب", title: "إعراب الجملة الاسمية", cls: "أول متوسط/3", due: "2026-08-27" },
{ id: 2, type: "واجب", title: "استخراج الأفعال من نص القراءة", cls: "أول متوسط/4", due: "2026-08-28" },
{ id: 3, type: "مشروع", title: "معجم مصور لعشر كلمات جديدة", cls: "أول متوسط/5", due: "2026-09-05" },
];
return {
students, tasks,
submissions: [],
diagnosticAttempts: {},
charterSigned: {},
learningStyleResults: {},
notifications: [
{ id: 1, audience: "all", text: "مرحبًا بكن في منصة لغتي التفاعلية لهذا الفصل الدراسي 🌿", date: "2026-08-20", read: false },
],
};
}
const CHARTER_LINES = [
"أحافظ على وقت الحصة وأشارك بأدب واحترام.",
"أؤدي واجباتي بنفسي وبأمانة دون نسخ من غيري.",
"أقرأ يوميًا ولو صفحة واحدة بصوت واضح.",
"أعتني بخطي وأكتب بعناية وترتيب.",
"أحترم زميلاتي ومعلمتي وأقبل الملاحظة بصدر رحب.",
"أسعى لتطوير لغتي لأنها هويتي.",
];
const DIAGNOSTIC_QUESTIONS = [
{ q: "ما نوع الجملة: «الطالبةُ مجتهدةٌ»؟", options: ["اسمية", "فعلية", "شرطية", "ندائية"], correct: 0 },
{ q: "أعرب كلمة «مجتهدةٌ» في المثال السابق:", options: ["فاعل", "مفعول به", "خبر", "مبتدأ"], correct: 2 },
{ q: "جمع كلمة «كتاب»:", options: ["كتب", "كتّاب", "كاتبون", "كتابات"], correct: 0 },
{ q: "مرادف كلمة «سعيد»:", options: ["حزين", "فرح", "غاضب", "خائف"], correct: 1 },
{ q: "ضد كلمة «كريم»:", options: ["بخيل", "شجاع", "طويل", "قوي"], correct: 0 },
{ q: "الفعل «كتبَ» فعل:", options: ["مضارع", "أمر", "ماضٍ", "منصوب"], correct: 2 },
{ q: "«يا طالبةُ» أسلوب:", options: ["استفهام", "نداء", "تعجب", "نهي"], correct: 1 },
{ q: "علامة رفع المثنى:", options: ["الضمة", "الألف", "الواو", "الكسرة"], correct: 1 },
{ q: "«هذا كتابٌ مفيدٌ» - كلمة «مفيدٌ»:", options: ["نعت", "بدل", "حال", "توكيد"], correct: 0 },
{ q: "جمع «معلمة» جمع مؤنث سالم:", options: ["معلمات", "معلمون", "معلمين", "معالم"], correct: 0 },
{ q: "حرف من حروف الجر:", options: ["إنّ", "على", "قد", "لن"], correct: 1 },
{ q: "الاسم الموصول للمفرد المؤنث:", options: ["الذي", "التي", "اللذان", "اللاتي"], correct: 1 },
{ q: "«لن» حرف:", options: ["جزم", "نصب", "جر", "عطف"], correct: 1 },
{ q: "مثنى كلمة «قلم»:", options: ["أقلام", "قلمان", "قلمين فقط", "أقلامان"], correct: 1 },
{ q: "الفاعل في «طارت العصافيرُ»:", options: ["طارت", "العصافير", "لا يوجد فاعل", "حرف جر"], correct: 1 },
{ q: "همزة الوصل تكتب في:", options: ["أكل", "استخرج", "أحمد", "إنسان"], correct: 1 },
{ q: "«ما أجملَ الحديقةَ» أسلوب:", options: ["تعجب", "استفهام", "شرط", "نفي"], correct: 0 },
{ q: "المفعول به في «قرأتُ الدرسَ»:", options: ["قرأتُ", "الدرسَ", "التاء", "لا يوجد"], correct: 1 },
{ q: "علامة نصب جمع المؤنث السالم:", options: ["الفتحة", "الكسرة", "الياء", "الواو"], correct: 1 },
{ q: "«كان الجوُّ صحوًا» - كلمة «صحوًا»:", options: ["اسم كان", "خبر كان", "فاعل", "مفعول مطلق"], correct: 1 },
];
const LEARNING_STYLE_QUESTIONS = [
{ q: "عندما أتعلم كلمة جديدة أفضّل أن:", options: [{ t: "أراها مكتوبة بخط واضح وألوان", s: "بصري" }, { t: "أسمعها منطوقة عدة مرات", s: "سمعي" }, { t: "أكتبها بيدي وأحركها", s: "حركي" }] },
{ q: "أتذكر الدرس بشكل أفضل عندما:", options: [{ t: "أشاهد صورًا أو خرائط ذهنية", s: "بصري" }, { t: "أستمع لشرح المعلمة", s: "سمعي" }, { t: "أطبّق وأتحرك أثناء الشرح", s: "حركي" }] },
{ q: "في وقت الفراغ أفضّل:", options: [{ t: "القراءة أو مشاهدة الصور", s: "بصري" }, { t: "الاستماع للقصص الصوتية", s: "سمعي" }, { t: "الأنشطة اليدوية والحركة", s: "حركي" }] },
{ q: "أفهم القاعدة النحوية أسرع عبر:", options: [{ t: "جدول أو رسم توضيحي", s: "بصري" }, { t: "شرح شفهي مكرر", s: "سمعي" }, { t: "تمارين تطبيقية مباشرة", s: "حركي" }] },
{ q: "عند حفظ نص أدبي أستخدم:", options: [{ t: "كتابته وتلوين الأفكار الرئيسة", s: "بصري" }, { t: "ترديده بصوت عالٍ", s: "سمعي" }, { t: "تمثيل معانيه بالحركة", s: "حركي" }] },
{ q: "أفضل طريقة لمراجعتي قبل الاختبار:", options: [{ t: "بطاقات وملخصات مرسومة", s: "بصري" }, { t: "تسجيل صوتي أستمع له", s: "سمعي" }, { t: "حل أسئلة وتمارين متكررة", s: "حركي" }] },
{ q: "في الحصة أنتبه أكثر عندما:", options: [{ t: "تُعرض شرائح أو صور", s: "بصري" }, { t: "تحكي المعلمة قصة", s: "سمعي" }, { t: "نقوم بنشاط جماعي", s: "حركي" }] },
{ q: "أصف نفسي بأنني:", options: [{ t: "ألاحظ التفاصيل البصرية بسرعة", s: "بصري" }, { t: "أحب الأحاديث والنقاشات", s: "سمعي" }, { t: "لا أستطيع الجلوس طويلًا دون حركة", s: "حركي" }] },
{ q: "عند القراءة الجهرية أفضّل أن:", options: [{ t: "أتابع النص بعيني بتركيز", s: "بصري" }, { t: "أستمع لقراءة نموذجية أولًا", s: "سمعي" }, { t: "أقرأ بصوت مرتفع وأنا أتحرك", s: "حركي" }] },
];
const TEACHER_TABS = [
{ id: "dashboard", label: "لوحة المعلمة", icon: LayoutDashboard },
{ id: "students", label: "الطالبات والفصول", icon: Users },
{ id: "attendance", label: "الحضور والغياب", icon: CalendarCheck },
{ id: "behavior", label: "السلوك والمشاركة", icon: MessageSquareText },
{ id: "profile", label: "ملف الإنجاز", icon: UserRound },
{ id: "assignments", label: "الواجبات", icon: ClipboardList },
{ id: "projects", label: "المشاريع", icon: FolderKanban },
{ id: "reading", label: "القراءة والتسجيل", icon: Mic },
{ id: "handwriting", label: "الخط", icon: PenTool },
{ id: "diagnostic", label: "الاختبار التشخيصي", icon: GraduationCap },
{ id: "charter", label: "ميثاق لغتي", icon: ScrollText },
{ id: "learningStyle", label: "أنماط التعلم", icon: Sparkles },
{ id: "submissions", label: "الأعمال المرسلة", icon: Inbox },
{ id: "grades", label: "الدرجات والتقييم", icon: ClipboardCheck },
{ id: "notifications", label: "الإشعارات", icon: Bell },
{ id: "reports", label: "التقارير والإحصائيات", icon: FileBarChart },
{ id: "qr", label: "رمز QR", icon: QrCode },
];
const STUDENT_TABS = [
{ id: "home", label: "الرئيسية", icon: Home },
{ id: "assignments", label: "الواجبات", icon: ClipboardList },
{ id: "projects", label: "المشاريع", icon: FolderKanban },
{ id: "reading", label: "القراءة والتسجيل", icon: Mic },
{ id: "handwriting", label: "الخط", icon: PenTool },
{ id: "diagnostic", label: "الاختبار التشخيصي", icon: GraduationCap },
{ id: "charter", label: "ميثاق لغتي", icon: ScrollText },
{ id: "learningStyle", label: "أنماط التعلم", icon: Sparkles },
{ id: "myWork", label: "أعمالي ودرجاتي", icon: Inbox },
{ id: "profile", label: "ملفي الشخصي", icon: UserRound },
{ id: "notifications", label: "الإشعارات", icon: Bell },
];
const PARENT_TABS = [
{ id: "overview", label: "نظرة عامة", icon: LayoutDashboard },
{ id: "work", label: "أعمال ابنتي", icon: Inbox },
{ id: "notifications", label: "الإشعارات", icon: Bell },
];
/* ================= tiny shared bits ================= */
function SectionHeader({ eyebrow, title, sub, accent = C.gold }) {
return (
{eyebrow}
{title}
{sub &&
{sub}
}
);
}
function ClassPicker({ value, onChange, classes = CLASSES }) {
return (
{classes.map((c) => (
))}
);
}
function Card({ children, style, className = "" }) {
return {children}
;
}
function Badge({ children, tone = "olive" }) {
const map = {
olive: [C.oliveSoft, C.olive], gold: [C.goldSoft, "#8A6412"], red: [C.redSoft, C.red],
indigo: [C.indigoSoft, C.indigo], pink: [C.pinkSoft, "#A45072"], aqua: [C.aquaSoft, C.aqua],
};
const [bg, fg] = map[tone];
return {children};
}
function EmptyState({ text }) {
return {text}
;
}
function fileToDataURL(file) {
return new Promise((resolve, reject) => {
const r = new FileReader();
r.onload = () => resolve(r.result);
r.onerror = reject;
r.readAsDataURL(file);
});
}
function todayISO() {
return new Date().toISOString().slice(0, 10);
}
/* ================= LANDING & LOGIN ================= */
function LandingScreen({ onPickRole }) {
const roles = [
{ id: "teacher", label: "المعلمة", desc: "إدارة الفصول والمتابعة والتقييم", icon: LayoutDashboard, tone: C.indigo, soft: C.indigoSoft },
{ id: "student", label: "الطالبة", desc: "واجباتي وأعمالي ودرجاتي", icon: GraduationCap, tone: C.aqua, soft: C.aquaSoft },
{ id: "parent", label: "ولي الأمر", desc: "متابعة أداء ابنتي", icon: UserRound, tone: C.pink, soft: C.pinkSoft },
];
return (
منصة لغتي التفاعلية
منصّة تعليميّة تفاعليّة للّغة العربيّة · إشراف الأستاذة عواطف الحربي
{MARQUEE_TEXT}{" • "}{MARQUEE_TEXT}{" • "}{MARQUEE_TEXT}
{roles.map(({ id, label, desc, icon: Icon, tone, soft }) => (
))}
);
}
function LoginScreen({ role, db, onBack, onSuccess }) {
const [error, setError] = useState("");
const roleMeta = {
teacher: { title: "دخول المعلمة", tone: C.indigo, soft: C.indigoSoft },
student: { title: "دخول الطالبة", tone: C.aqua, soft: C.aquaSoft },
parent: { title: "دخول ولي الأمر", tone: C.pink, soft: C.pinkSoft },
}[role];
const [teacherUser, setTeacherUser] = useState("");
const [teacherPass, setTeacherPass] = useState("");
const [cls, setCls] = useState(CLASSES[0]);
const studentsInClass = db.students.filter((s) => s.cls === cls);
const [studentId, setStudentId] = useState(studentsInClass[0]?.id || "");
const [pin, setPin] = useState("");
const [search, setSearch] = useState("");
const matches = db.students.filter((s) => s.name.includes(search.trim()));
const [parentPin, setParentPin] = useState("");
const [parentTarget, setParentTarget] = useState(null);
function submitTeacher() {
if (teacherUser.trim() === "المعلمة" && teacherPass === "1234") onSuccess({ role: "teacher" });
else setError("اسم المستخدم أو كلمة المرور غير صحيحة");
}
function submitStudent() {
const st = db.students.find((s) => s.id === Number(studentId));
if (st && pin === st.pin) onSuccess({ role: "student", studentId: st.id });
else setError("رمز الدخول غير صحيح");
}
function submitParent() {
if (parentTarget && parentPin === "0000") onSuccess({ role: "parent", studentId: parentTarget.id });
else setError("تأكدي من اختيار الطالبة وإدخال الرمز الصحيح");
}
return (
{role === "teacher" && (
setTeacherUser(e.target.value)}
className="font-body w-full text-sm p-3 rounded-xl border outline-none" style={{ borderColor: C.line }} />
setTeacherPass(e.target.value)}
className="font-body w-full text-sm p-3 rounded-xl border outline-none" style={{ borderColor: C.line }} />
تجريبي: المستخدم «المعلمة» وكلمة المرور «1234»
{error &&
{error}
}
)}
{role === "student" && (
setPin(e.target.value)}
className="font-body w-full text-sm p-3 rounded-xl border outline-none" style={{ borderColor: C.line }} />
تجريبي: الرمز «1111» لجميع الطالبات
{error &&
{error}
}
)}
{role === "parent" && (
{ setSearch(e.target.value); setParentTarget(null); }}
className="font-body w-full text-sm p-3 rounded-xl border outline-none" style={{ borderColor: C.line }} />
{search && (
{matches.slice(0, 6).map((s) => (
))}
{matches.length === 0 &&
لا توجد نتائج
}
)}
setParentPin(e.target.value)}
className="font-body w-full text-sm p-3 rounded-xl border outline-none" style={{ borderColor: C.line }} />
تجريبي: الرمز «0000»
{error &&
{error}
}
)}
);
}
/* ================= shared submission form ================= */
function SubmitWork({ type, tasksForType, studentId, db, persist, kind }) {
const [taskId, setTaskId] = useState(tasksForType[0]?.id || "");
const [text, setText] = useState("");
const [fileInfo, setFileInfo] = useState(null);
const [busy, setBusy] = useState(false);
const [done, setDone] = useState(false);
const student = db.students.find((s) => s.id === studentId);
async function handleFile(e) {
const f = e.target.files?.[0];
if (!f) return;
try {
const dataUrl = await fileToDataURL(f);
setFileInfo({ dataUrl, name: f.name, mime: f.type });
} catch {
setFileInfo(null);
}
}
function submit() {
if (kind === "file" && !fileInfo) return;
if (kind === "text" && !text.trim()) return;
setBusy(true);
const task = tasksForType.find((t) => t.id === Number(taskId));
const submission = {
id: Date.now(),
studentId, type,
title: task ? task.title : type,
cls: student.cls,
date: todayISO(),
status: "جديد", grade: null, note: "",
attachment: kind === "file" ? { kind: type === "قراءة" ? "audio" : "image", ...fileInfo } : { kind: "text", text },
};
const newDb = { ...db, submissions: [submission, ...db.submissions] };
persist(newDb);
setBusy(false); setDone(true); setText(""); setFileInfo(null);
setTimeout(() => setDone(false), 2500);
}
return (
{tasksForType.length > 0 && (
)}
{tasksForType.length === 0 && (
لا توجد مهام {type === "واجب" ? "واجبات" : "مشاريع"} لفصلكِ حاليًا، يمكنكِ رغم ذلك إرسال عمل حر.
)}
{kind === "text" && (
);
}
function MyWorkList({ studentId, db }) {
const mine = db.submissions.filter((s) => s.studentId === studentId);
if (mine.length === 0) return ;
return (
{mine.map((s) => (
{s.title}
{s.type} · {s.date}
{s.status === "مقيمة" && s.note &&
ملاحظة: {s.note}
}
{s.status === "مقيمة" ? (
الدرجة {s.grade}/10
) : (
بانتظار التقييم
)}
))}
);
}
/* ================= TEACHER PAGES ================= */
function TeacherDashboard({ db }) {
const total = db.students.length;
const pending = db.submissions.filter((s) => s.status === "جديد").length;
const weekStars = db.students.reduce((sum, s) => sum + s.points, 0);
const signedCharter = Object.keys(db.charterSigned).length;
const classAverages = CLASSES.map((c) => {
const inClass = db.students.filter((s) => s.cls === c);
const avg = inClass.reduce((s2, st) => s2 + st.points, 0) / (inClass.length || 1);
return { cls: c, avg };
}).sort((a, b) => b.avg - a.avg);
const maxAvg = Math.max(...classAverages.map((c) => c.avg), 1);
const stats = [
{ label: "إجمالي الطالبات", value: total, icon: Users },
{ label: "أعمال بانتظار التقييم", value: pending, icon: Inbox },
{ label: "نجمات لغتي الإجمالية", value: weekStars, icon: Star },
{ label: "موقّعات ميثاق لغتي", value: `${signedCharter}/${total}`, icon: ScrollText, small: true },
];
const recent = [...db.submissions].slice(0, 5);
return (
{MARQUEE_TEXT}{" • "}{MARQUEE_TEXT}{" • "}{MARQUEE_TEXT}
{stats.map(({ label, value, icon: Icon, small }) => (
{value}
{label}
))}
ترتيب الفصول بمعدل النجمات
{classAverages.map(({ cls, avg }, i) => (
{i + 1}
{cls}
{avg.toFixed(1)}
))}
أحدث الأعمال المرسلة
{recent.length === 0 && }
{recent.map((s) => {
const st = db.students.find((x) => x.id === s.studentId);
return (
{st?.name} — {s.title}
{s.status}
);
})}
);
}
function StudentsTab({ db, persist }) {
const [filterClass, setFilterClass] = useState(CLASSES[0]);
const [newName, setNewName] = useState("");
const filtered = db.students.filter((s) => s.cls === filterClass);
function adjustPoints(id, delta) {
persist({ ...db, students: db.students.map((s) => s.id === id ? { ...s, points: Math.max(0, s.points + delta) } : s) });
}
function addStudent() {
if (!newName.trim()) return;
const id = Date.now();
persist({ ...db, students: [...db.students, { id, name: newName.trim(), cls: filterClass, pin: "1111", parentName: "ولي أمر " + newName.trim().split(" ")[0], points: 0, attendance: {}, behaviorLog: [], skills: { reading: 0, handwriting: 0 } }] });
setNewName("");
}
return (
{filtered.map((s) => (
{s.name.charAt(0)}
{s.name}
{s.points} نجمة · {s.parentName}
))}
{filtered.length === 0 &&
}
);
}
function AttendanceTab({ db, persist }) {
const [cls, setCls] = useState(CLASSES[0]);
const [saved, setSaved] = useState(false);
const date = todayISO();
const list = db.students.filter((s) => s.cls === cls);
function setStatus(id, status) {
persist({ ...db, students: db.students.map((s) => s.id === id ? { ...s, attendance: { ...s.attendance, [date]: status } } : s) });
setSaved(false);
}
return (
{list.map((s) => {
const st = s.attendance[date];
return (
{s.name}
);
})}
{saved &&
✓ تم الحفظ}
);
}
function BehaviorTab({ db, persist }) {
const [cls, setCls] = useState(CLASSES[0]);
const [openId, setOpenId] = useState(null);
const [note, setNote] = useState("");
const list = db.students.filter((s) => s.cls === cls);
function addNote(id, type) {
if (!note.trim()) return;
persist({
...db, students: db.students.map((s) => s.id === id
? { ...s, points: Math.max(0, s.points + (type === "positive" ? 1 : -1)), behaviorLog: [{ text: note.trim(), type, date: todayISO() }, ...s.behaviorLog] }
: s)
});
setNote(""); setOpenId(null);
}
return (
{list.map((s) => (
{openId === s.id && (
)}
))}
);
}
function StudentProfile({ db, persist, forStudentId, readOnly }) {
const [cls, setCls] = useState(CLASSES[0]);
const [sid, setSid] = useState(forStudentId || null);
const student = db.students.find((s) => s.id === (forStudentId || sid));
const list = db.students.filter((s) => s.cls === cls);
function setSkill(key, val) {
persist({ ...db, students: db.students.map((s) => s.id === student.id ? { ...s, skills: { ...s.skills, [key]: val } } : s) });
}
const attendanceEntries = student ? Object.values(student.attendance) : [];
const presentRate = attendanceEntries.length ? Math.round((attendanceEntries.filter((a) => a === "present").length / attendanceEntries.length) * 100) : null;
const mySubs = student ? db.submissions.filter((s) => s.studentId === student.id) : [];
const graded = mySubs.filter((s) => s.status === "مقيمة");
const avgGrade = graded.length ? (graded.reduce((a, b) => a + (b.grade || 0), 0) / graded.length).toFixed(1) : null;
const diag = student ? db.diagnosticAttempts[student.id] : null;
const charter = student ? db.charterSigned[student.id] : null;
const learning = student ? db.learningStyleResults[student.id] : null;
return (
{!forStudentId && (
<>
{ setCls(c); setSid(null); }} />
{list.map((s) => (
))}
>
)}
{!student && }
{student && (
{student.name.charAt(0)}
{student.name}
{student.cls} · {student.points} نجمة
{presentRate === null ? "—" : `${presentRate}%`}
نسبة الحضور
{avgGrade ?? "—"}
معدل الدرجات /10
{diag ? `${diag.score}/20` : "—"}
الاختبار التشخيصي
{charter ? "موقّع ✓" : "لم يوقَّع"}
ميثاق لغتي
المهارات اللغوية
{["reading", "handwriting"].map((k) => (
{k === "reading" ? "القراءة الجهرية" : "الخط العربي"}
{Array.from({ length: 5 }).map((_, i) => (
))}
))}
{learning && (
نمط التعلم المفضل
{learning.type}
)}
آخر الملاحظات السلوكية
{student.behaviorLog.length === 0 && }
{student.behaviorLog.slice(0, 5).map((l, i) => (
- {l.text}
))}
)}
);
}
function TasksAdminAndSubmit({ db, persist, type, role, studentId }) {
const [cls, setCls] = useState(CLASSES[0]);
const [title, setTitle] = useState("");
const [due, setDue] = useState("");
function addTask() {
if (!title.trim()) return;
persist({ ...db, tasks: [...db.tasks, { id: Date.now(), type, title: title.trim(), cls, due: due || todayISO() }] });
setTitle(""); setDue("");
}
if (role === "teacher") {
const list = db.tasks.filter((t) => t.type === type);
return (
);
}
const student = db.students.find((s) => s.id === studentId);
const tasksForType = db.tasks.filter((t) => t.type === type && t.cls === student.cls);
return (
);
}
function ReadingHandwritingPage({ db, persist, type, role, studentId }) {
if (role !== "teacher") {
return (
);
}
const items = db.submissions.filter((s) => s.type === type);
return (
{items.length === 0 &&
}
{items.map((s) => {
const st = db.students.find((x) => x.id === s.studentId);
return (
{st?.name} · {st?.cls}
{s.status}
{s.attachment.kind === "audio" && }
{s.attachment.kind === "image" &&
}
);
})}
);
}
function DiagnosticPage({ db, persist, role, studentId }) {
const [answers, setAnswers] = useState(Array(20).fill(null));
const [submitted, setSubmitted] = useState(false);
if (role === "teacher") {
const attempts = Object.entries(db.diagnosticAttempts);
return (
{attempts.length === 0 &&
}
{attempts.map(([sid, a]) => {
const st = db.students.find((x) => x.id === Number(sid));
return (
{st?.name} · {st?.cls}
= 15 ? "olive" : a.score >= 10 ? "gold" : "red"}>{a.score}/20
);
})}
);
}
const attempt = db.diagnosticAttempts[studentId];
if (attempt) {
return (
{attempt.score}/20
لقد أتممتِ الاختبار بتاريخ {attempt.date} — المحاولة الواحدة المسموحة مستخدمة
);
}
function submit() {
const score = DIAGNOSTIC_QUESTIONS.reduce((acc, q, i) => acc + (answers[i] === q.correct ? 1 : 0), 0);
persist({ ...db, diagnosticAttempts: { ...db.diagnosticAttempts, [studentId]: { score, date: todayISO() } } });
setSubmitted(true);
}
const answeredAll = answers.every((a) => a !== null);
if (submitted) return null;
return (
{DIAGNOSTIC_QUESTIONS.map((q, i) => (
{i + 1}. {q.q}
{q.options.map((opt, oi) => (
))}
))}
{!answeredAll &&
أجيبي عن كل الأسئلة قبل الإرسال
}
);
}
function CharterPage({ db, persist, role, studentId }) {
if (role === "teacher") {
return (
{CHARTER_LINES.map((l, i) => - • {l}
)}
{db.students.map((s) => (
{s.name} · {s.cls}
{db.charterSigned[s.id] ? موقّع {db.charterSigned[s.id]} : لم يوقَّع}
))}
);
}
const signed = db.charterSigned[studentId];
return (
{CHARTER_LINES.map((l, i) => - {l}
)}
{signed ? (
وقّعتِ الميثاق بتاريخ {signed}
) : (
)}
);
}
function LearningStylePage({ db, persist, role, studentId }) {
const [answers, setAnswers] = useState(Array(LEARNING_STYLE_QUESTIONS.length).fill(null));
if (role === "teacher") {
const results = Object.entries(db.learningStyleResults);
const counts = { بصري: 0, سمعي: 0, حركي: 0 };
results.forEach(([, r]) => counts[r.type]++);
return (
{Object.entries(counts).map(([k, v]) => (
{v}
{k}
))}
{results.map(([sid, r]) => {
const st = db.students.find((x) => x.id === Number(sid));
return {st?.name}{r.type};
})}
{results.length === 0 && }
);
}
const result = db.learningStyleResults[studentId];
if (result) {
return (
{result.type}
حُلّلت بتاريخ {result.date}
);
}
function submit() {
const tally = { بصري: 0, سمعي: 0, حركي: 0 };
answers.forEach((a, i) => { if (a !== null) tally[LEARNING_STYLE_QUESTIONS[i].options[a].s]++; });
const type = Object.entries(tally).sort((a, b) => b[1] - a[1])[0][0];
persist({ ...db, learningStyleResults: { ...db.learningStyleResults, [studentId]: { type, date: todayISO() } } });
}
const answeredAll = answers.every((a) => a !== null);
return (
{LEARNING_STYLE_QUESTIONS.map((q, i) => (
{i + 1}. {q.q}
{q.options.map((opt, oi) => (
))}
))}
);
}
function SubmissionsInbox({ db, persist }) {
const [openId, setOpenId] = useState(null);
const [grade, setGrade] = useState("");
const [note, setNote] = useState("");
const pending = db.submissions.filter((s) => s.status === "جديد");
function saveGrade(sub) {
const g = Math.max(0, Math.min(10, Number(grade) || 0));
const newSubs = db.submissions.map((s) => s.id === sub.id ? { ...s, status: "مقيمة", grade: g, note } : s);
const notif = { id: Date.now(), audience: sub.studentId, text: `تم تقييم عملك «${sub.title}» — الدرجة ${g}/10`, date: todayISO(), read: false };
persist({ ...db, submissions: newSubs, notifications: [notif, ...db.notifications] });
setOpenId(null); setGrade(""); setNote("");
}
return (
{pending.length === 0 &&
}
{pending.map((s) => {
const st = db.students.find((x) => x.id === s.studentId);
return (
{openId === s.id && (
{s.attachment.kind === "text" &&
{s.attachment.text}
}
{s.attachment.kind === "audio" &&
}
{s.attachment.kind === "image" &&

}
setGrade(e.target.value)} placeholder="الدرجة /10" type="number" min="0" max="10"
className="font-body text-sm p-2 rounded-lg border outline-none w-28" style={{ borderColor: C.line }} />
setNote(e.target.value)} placeholder="ملاحظة للطالبة"
className="font-body text-sm p-2 rounded-lg border outline-none flex-1" style={{ borderColor: C.line }} />
)}
);
})}
);
}
function GradesOverview({ db }) {
const [cls, setCls] = useState(CLASSES[0]);
const graded = db.submissions.filter((s) => s.status === "مقيمة" && db.students.find((st) => st.id === s.studentId)?.cls === cls);
return (
{graded.length === 0 &&
}
{graded.map((s) => {
const st = db.students.find((x) => x.id === s.studentId);
return (
{st?.name} — {s.title}
{s.grade}/10
{s.note && {s.note}
}
);
})}
);
}
function NotificationsPage({ db, persist, role, studentId }) {
const [text, setText] = useState("");
const [audience, setAudience] = useState("all");
const visible = role === "teacher" ? db.notifications
: db.notifications.filter((n) => n.audience === "all" || n.audience === studentId);
function send() {
if (!text.trim()) return;
persist({ ...db, notifications: [{ id: Date.now(), audience: audience === "all" ? "all" : Number(audience), text: text.trim(), date: todayISO(), read: false }, ...db.notifications] });
setText("");
}
return (
{role === "teacher" && (
)}
{visible.map((n) => (
))}
{visible.length === 0 &&
}
);
}
function ReportsPage({ db }) {
const total = db.students.length;
const submissionsByType = ["واجب", "مشروع", "قراءة", "خط"].map((t) => ({ t, n: db.submissions.filter((s) => s.type === t).length }));
const maxSub = Math.max(...submissionsByType.map((s) => s.n), 1);
const graded = db.submissions.filter((s) => s.status === "مقيمة");
const avgGrade = graded.length ? (graded.reduce((a, b) => a + b.grade, 0) / graded.length).toFixed(1) : "—";
const diagAvg = Object.values(db.diagnosticAttempts).length
? (Object.values(db.diagnosticAttempts).reduce((a, b) => a + b.score, 0) / Object.values(db.diagnosticAttempts).length).toFixed(1) : "—";
const charterPct = Math.round((Object.keys(db.charterSigned).length / total) * 100);
return (
{avgGrade}
معدل درجات الأعمال
{diagAvg}
معدل الاختبار التشخيصي
{charterPct}%
نسبة توقيع الميثاق
{db.submissions.length}
إجمالي الأعمال المرسلة
الأعمال حسب النوع
{submissionsByType.map(({ t, n }) => (
))}
);
}
function QRPage({ db }) {
const [cls, setCls] = useState(CLASSES[0]);
const list = db.students.filter((s) => s.cls === cls);
return (
{list.map((s) => (
{s.name}
))}
* تُولَّد الرموز عبر خدمة خارجية وتتطلب اتصالًا بالإنترنت لعرضها.
);
}
/* ================= STUDENT home ================= */
function StudentHome({ db, studentId }) {
const student = db.students.find((s) => s.id === studentId);
const pendingTasks = db.tasks.filter((t) => t.cls === student.cls).length;
const notifCount = db.notifications.filter((n) => n.audience === "all" || n.audience === studentId).length;
return (
{MARQUEE_TEXT}{" • "}{MARQUEE_TEXT}
{student.points}
نجماتي
{pendingTasks}
مهام فصلي
{notifCount}
إشعارات
);
}
/* ================= PARENT pages ================= */
function ParentOverview({ db, studentId }) {
const student = db.students.find((s) => s.id === studentId);
const attendanceEntries = Object.values(student.attendance);
const presentRate = attendanceEntries.length ? Math.round((attendanceEntries.filter((a) => a === "present").length / attendanceEntries.length) * 100) : null;
const graded = db.submissions.filter((s) => s.studentId === studentId && s.status === "مقيمة");
const avgGrade = graded.length ? (graded.reduce((a, b) => a + b.grade, 0) / graded.length).toFixed(1) : "—";
return (
{student.points}
النجمات
{presentRate === null ? "—" : `${presentRate}%`}
نسبة الحضور
{avgGrade}
معدل الدرجات
{db.charterSigned[studentId] ? "موقّع" : "—"}
ميثاق لغتي
المهارات اللغوية
{[["reading", "القراءة الجهرية"], ["handwriting", "الخط العربي"]].map(([k, label]) => (
{label}
{Array.from({ length: 5 }).map((_, i) => )}
))}
);
}
/* ================= SHELL ================= */
function Shell({ role, studentId, db, persist, tabs, onLogout, activeTab, setActiveTab, roleAccent, roleLabel }) {
let content;
if (role === "teacher") {
content = {
dashboard: ,
students: ,
attendance: ,
behavior: ,
profile: ,
assignments: ,
projects: ,
reading: ,
handwriting: ,
diagnostic: ,
charter: ,
learningStyle: ,
submissions: ,
grades: ,
notifications: ,
reports: ,
qr: ,
}[activeTab];
} else if (role === "student") {
content = {
home: ,
assignments: ,
projects: ,
reading: ,
handwriting: ,
diagnostic: ,
charter: ,
learningStyle: ,
myWork: (
),
profile: ,
notifications: ,
}[activeTab];
} else {
content = {
overview: ,
work: (
),
notifications: ,
}[activeTab];
}
return (
{content}
);
}
/* ================= APP ================= */
export default function App() {
const [stage, setStage] = useState("landing"); // landing | login | app
const [pendingRole, setPendingRole] = useState(null);
const [session, setSession] = useState(null);
const [db, setDb] = useState(null);
const [activeTab, setActiveTab] = useState(null);
useEffect(() => {
let alive = true;
(async () => {
try {
const res = await window.storage.get("lughati-db-v1", true);
if (alive) setDb(res ? JSON.parse(res.value) : makeInitialDB());
} catch {
if (alive) setDb(makeInitialDB());
}
})();
return () => { alive = false; };
}, []);
async function persist(newDb) {
setDb(newDb);
try { await window.storage.set("lughati-db-v1", JSON.stringify(newDb), true); } catch {}
}
function handlePickRole(role) { setPendingRole(role); setStage("login"); }
function handleLoginSuccess(s) {
setSession(s);
setActiveTab(s.role === "teacher" ? "dashboard" : s.role === "student" ? "home" : "overview");
setStage("app");
}
function logout() { setSession(null); setPendingRole(null); setStage("landing"); }
if (!db) {
return جارِ التحميل...
;
}
if (stage === "landing") return ;
if (stage === "login") return setStage("landing")} onSuccess={handleLoginSuccess} />;
const tabs = session.role === "teacher" ? TEACHER_TABS : session.role === "student" ? STUDENT_TABS : PARENT_TABS;
const roleLabel = session.role === "teacher" ? "المعلمة" : session.role === "student" ? "طالبة" : "ولي أمر";
const roleAccent = session.role === "teacher" ? "indigo" : session.role === "student" ? "aqua" : "pink";
return (
);
}